#ifndef __PROCESSPOOL_H_
#define __PROCESSPOOL_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>

class process{
    public:
        int pid; //进程号
        int pipe[2]; //每个进程有一个管道，这个管道原本可以直接用linux提供的半双工通信pipe创建，但本次我们使用全双工通信socketpair函数创建到pipe数组中

        process():pid(-1),pipe{0,0}{}
        ~process(){}
};

template<typename T>
class processpool{
    private:
        static const int MAX_EVENTS_NUMBER=5;
        static const int MAX_USER_PER_PROCESS=10000;
        int idx;
        int listenfd;
        int epollfd;
        int max_processes_num;
        process* sub_processes;
        static processpool<T>* instance;//使用单例模式创建进程池

        processpool(int listenfd,int max_processes_num=8);
        ~processpool(){
            delete[] sub_processes;
        }

    public:
    static processpool<T>* create(int listenfd,int _max_processes_num=8){
        if(instance==nullptr){
            instance=new processpool<T>(listenfd,_max_processes_num=8);
            return instance;
        }
        return instance;
    }

    void run();
    void run_parent();
    void run_child();
    void setup_up_sig();
};


template<typename T>
processpool<T>* processpool<T>::instance=nullptr;

template<typename T>
processpool<T>::processpool(int listenfd,int _max_processes_num):
    idx(-1),listenfd(listenfd),max_processes_num(_max_processes_num),sub_processes(nullptr){
            sub_processes=new process[max_processes_num];
//进程池里要通过fork()创建max_processes_num个子进程，并且父子进程的通信管道要通过 socketpair 创建好

//所以你看for循环里是父进程的话用continue，继续创建，子进程的话用break，处理完就退出，千万不要让子进程也进循环里fork。
         for(int i=0;i<max_processes_num;i++){
             socketpair(PF_UNIX,SOCK_STREAM,0,sub_processes[i].pipe);
        /*
            linux中的管道本来是叫pipe，pipe的创建是用os.pipe(),pipe规定死的必须是一个长度为2的数组pipe[0]位置存放读端，pipe[1]的位置存放
            写端。 当父进程fork时，子进程也会使用这个管道pipe。 pipe也只能用在父子进程之间或兄弟进程之间。 pipe的一个缺点是只能半双工通信，
            就是pipe[0]必须是读，pipe[1]必须是写。 所以引出了新的创建管道的方式————socketpair

            socketpair()函数将创建出的管道放在第四个参数中，和pipe一样只能用在父子进程之间或兄弟进程之间。不同的是socketpair创建的管道支持
            全双工通信，自己想用哪个做读端哪个做写端都行，所以会看到下面的父进程close(sub_processes[i].pipe[1])，意思是父进程只通过[0]写，
            [1]父进程不用，同理子进程 close(sub_processes[i].pipe[0])，只用[1]读。

            之前一直看不懂这一片儿的代码，原因是不知道父进程fork()时子进程也会用这个管道，也就是两者用的管道是一致的，本代码中就是父进程的管道
            其实一直都是初始化的{0,0}一直都没用，可以理解为父子进程之间的通信都靠 socketpair为sub_processes[i].pipe创建的管道

        */
             sub_processes[i].pid=fork();

         /*
         对fork()函数要有了解，fork()函数的返回值有三种
         1. <0,说明创建子进程失败
         2. ==0，说明当前执行的进程是子进程
         3. >0，说明当前执行的是父进程，并且，返回的这个值是子进程的pid(别弄错哦，是子进程pid)
         其次，fork()的一大特性就是一次调用，两次返回。 这个别太纠结，其实就是父进程和子进程各返回一次
         */

             if(sub_processes[i].pid>0){ 
                 close(sub_processes[i].pipe[1]);//父进程只用[0]写
                 //父进程的idx为-1
                 continue;
             }
             else{
                 close(sub_processes[i].pipe[0]);//子进程只用[1]读
                 idx=i;//子进程有自己的标号
                 break;
             }
         }
     }

static int set_non_blocking(int fd){
    int old_state=fcntl(fd,F_GETFL);
    int new_state=old_state|O_NONBLOCK;
    fcntl(fd,F_SETFL,new_state);

    return old_state;
}

static void addfd(int epollfd,int fd){
    epoll_event event;
    event.events=EPOLLIN|EPOLLET;
    event.data.fd=fd;
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
    set_non_blocking(fd);
}

static void removefd(int epollfd,int fd){
    epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,nullptr);
    close(fd);
}

template <typename T>
void processpool<T>::run(){
    if(idx==-1){
        run_parent();
    }else{
        run_child();
    }
}


template<typename T>
void processpool<T>::setup_up_sig(){
    epollfd=epoll_create(5);
    assert(epollfd!=-1);
}


template<typename T>
//run_parent()做的事情就是给每个请求的客户端找一个子进程来处理请求
void processpool<T>::run_parent(){
    epoll_event events[MAX_EVENTS_NUMBER];
    setup_up_sig();

    addfd(epollfd,listenfd);

    int pre_idx=0;
    int has_new_cli=1;
    int number=0;
    while(1){
        number=epoll_wait(epollfd,events,MAX_EVENTS_NUMBER,-1);
        for(int i=0;i<number;i++){
            int sockfd=events[i].data.fd;
            if(sockfd==listenfd){
                int pos=pre_idx;
                do{
                    pos=(pos+1)%max_processes_num;
                }while(sub_processes[pos].pid==-1); //意思就是这个进程没有被分配资源，我就一直在这儿等着分配到资源
                pre_idx=pos;

                send(sub_processes[pos].pipe[0],(void*)&has_new_cli,sizeof(has_new_cli),0);
                printf("parent processes has sent msg to %d childe\n",pos);
            }
        }
    }
}


template<typename T>
void processpool<T>::run_child(){
    epoll_event events[MAX_EVENTS_NUMBER];
    setup_up_sig();

    int pipefd=sub_processes[idx].pipe[1];
    addfd(epollfd,pipefd);
    T* users=new T[MAX_USER_PER_PROCESS];

    int number=0;
    while(1){
        number=epoll_wait(epollfd,events,MAX_EVENTS_NUMBER,-1);
        for(int i=0;i<number;i++){
            int sockfd=events[i].data.fd;
            if(sockfd==pipefd&&(events[i].events&EPOLLIN)){ //客户端要读
                struct sockaddr_in client;
                socklen_t client_addrlength=sizeof(client);
                int connfd=accept(listenfd,(struct sockaddr*)(&client),&client_addrlength);
                addfd(epollfd,connfd);
                users[connfd].init(epollfd,connfd,client);
                printf("child %d is addfding\n",idx);
                continue;
            }
            else if(events[i].events&EPOLLIN){
                printf("child %d has recv msg\n",idx);
                users[sockfd].process();
            }
        }
    }

    delete[] users;
    users=nullptr;

    close(epollfd);
    close(pipefd);
}
#endif

